<!-- 虚拟列表组件-竖向滚动
    1.可设置指定数量渲染；
    2.列表内右侧有滚动到顶部或底部快捷按钮；
    3.采用插槽的方式生成显示；
    4.每个item为占满一行；
-->
<template>
	<div
		class="virtual-list-container"
		ref="containerRef"
		:style="{ height: containerHeight + 'px', overflow: 'auto' }"
		@scroll="handleScroll"
	>
		<!-- 顶部空白占位 -->
		<div :style="{ height: getOffsetTop() }"></div>

		<!-- 可视区域的真实dom -->
		<div
			class="virtual-list-item"
			v-for="item in visibleItems"
			:key="item.index"
			:style="{ height: itemHeight + 'px' }"
		>
			<!-- 插槽传递 item -->
			<slot :item="item.data" :index="item.index"></slot>
		</div>

		<!-- 底部空白占位 -->

		<div :style="{ height: getOffsetBottom() }"></div>
	</div>
</template>

<script setup>
import { ref, computed, onMounted, defineProps } from "vue";

const props = defineProps({
	// 总列表
	list: {
		type: Array,
		required: true,
	},
	// 每行单元的高度（px）
	itemHeight: {
		type: Number,
		default: 50,
	},
	// 容器高度
	containerHeight: {
		type: Number,
		default: 400,
	},
});

//  创建 ref 对象
const containerRef = ref(null);
// 滚出值
const scrollTop = ref(0);

// 多渲染数量，防止白屏（缓冲）
const bufferSize = 5;

// 可见项数量（向上取整）
const visibleCount = computed(() => {
	return Math.ceil(props.containerHeight / props.itemHeight);
});

// 渲染列表单元的开始索引
const startIndex = computed(() => {
	return Math.max(
		0,
		Math.floor(scrollTop.value / props.itemHeight) - bufferSize
	);
});

// 渲染列表单元的结束索引
const endIndex = computed(() => {
	// 限制不超出列表的上限索引
	return Math.min(
		props.list.length - 1,
		startIndex.value + visibleCount.value + bufferSize
	);
});

// 则当前应渲染的可见项
const visibleItems = computed(() => {
	return props.list
		.slice(startIndex.value, endIndex.value + 1)
		.map((data, i) => ({
			data,
			index: startIndex.value + i,
		}));
});

// 列表单元总数
const total = computed(() => {
	return props.list.length;
});

// 上方占位高度
const getOffsetTop = () => {
	const res = startIndex.value * props.itemHeight;

	return res + "px";
};

// 下方占位高度
const getOffsetBottom = () => {
	const res = (total.value - endIndex.value - 1) * props.itemHeight;

	return res + "px";
};

// 列表滚动事件
const handleScroll = () => {
	if (containerRef.value) {
		scrollTop.value = containerRef.value.scrollTop;
	}
};

// 滚动到指定的 index
const scrollToIndex = (index) => {
	if (containerRef.value) {
		containerRef.value.scrollTop = index * props.itemHeight;
		scrollTop.value = containerRef.value.scrollTop;
	}
};

// 对外暴露方法
defineExpose({ scrollToIndex });
</script>

<style scoped lang="less" src="./virtual-list.less"></style>
